﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;

namespace ReactiveLang.Collections
{
    using Util;

    [ContractClass(typeof(IEnumerableExpressionContract<>))]
    public interface IEnumerableExpression<out T>
    {
        IDisposable Activate(IEnumerableExpressionObserver<T> observer);
    }

    #region Contract of IEnumerableExpression<T>

    [ContractClassFor(typeof(IEnumerableExpression<>))]
    abstract class IEnumerableExpressionContract<T> : IEnumerableExpression<T>
    {
        public IDisposable Activate(IEnumerableExpressionObserver<T> observer)
        {
            Contract.Requires<ArgumentNullException>(observer != null, "observer");
            Contract.Ensures(Contract.Result<IDisposable>() != null);
            return null;
        }
    }

    #endregion

    public static partial class EnumerableExpression
    {
        #region Create

        class AnonymousObservableEnumerable<T> : IEnumerableExpression<T>
        {
            internal Func<IEnumerableExpressionObserver<T>, IDisposable> _activate;
            public IDisposable Activate(IEnumerableExpressionObserver<T> observer)
            {
                return _activate(observer);
            }
        }
        public static IEnumerableExpression<T> CreateWithDisposable<T>(Func<IEnumerableExpressionObserver<T>, IDisposable> activate)
        {
            Contract.Requires<ArgumentNullException>(activate != null, "activate");
            return new AnonymousObservableEnumerable<T> { _activate = activate };
        }
        public static IEnumerableExpression<T> Create<T>(Func<IEnumerableExpressionObserver<T>, Action> activate)
        {
            Contract.Requires<ArgumentNullException>(activate != null, "activate");
            return CreateWithDisposable<T>(o => Disposable.Create(activate(o)));
        }

        #endregion

        #region Activate

        public static IDisposable Activate<T>(this IEnumerableExpression<T> source, Action<IExpression<T>, int> onAdded, Action<IExpression<T>, int> onRemoved,
            Action<IEnumerable<IExpression<T>>> onValue, Action<Exception> onException)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");
            Contract.Requires<ArgumentNullException>(onAdded != null, "onAdded");
            Contract.Requires<ArgumentNullException>(onRemoved != null, "onRemoved");
            Contract.Requires<ArgumentNullException>(onValue != null, "onValue");
            Contract.Requires<ArgumentNullException>(onException != null, "onException");
            return source.Activate(EnumerableExpressionObserver.Create(onAdded, onRemoved, onValue, onException));
        }
        public static IDisposable Activate<T>(this IEnumerableExpression<T> source, Action<IExpression<T>, int> onAdded, Action<IExpression<T>, int> onRemoved,
            Action<IEnumerable<IExpression<T>>> onValue)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");
            Contract.Requires<ArgumentNullException>(onAdded != null, "onAdded");
            Contract.Requires<ArgumentNullException>(onRemoved != null, "onRemoved");
            Contract.Requires<ArgumentNullException>(onValue != null, "onValue");
            return source.Activate(onAdded, onRemoved, onValue, ex => { throw ex; });
        }

        public static IDisposable ActivateWithClear<T>(this IEnumerableExpression<T> source, Action<IExpression<T>, int> onAdded, Action<IExpression<T>, int> onRemoved, 
            Action onCleared, Action<Exception> onException)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");
            Contract.Requires<ArgumentNullException>(onAdded != null, "onAdded");
            Contract.Requires<ArgumentNullException>(onRemoved != null, "onRemoved");
            Contract.Requires<ArgumentNullException>(onCleared != null, "onCleared");
            Contract.Requires<ArgumentNullException>(onException != null, "onException");
            return source.Activate(onAdded, onRemoved,
                next =>
                {
                    onCleared();
                    int i = 0;
                    foreach (var item in next)
                        onAdded(item, i++);
                },
                onException);
        }
        public static IDisposable ActivateWithClear<T>(this IEnumerableExpression<T> source, Action<IExpression<T>, int> onAdded, Action<IExpression<T>, int> onRemoved, Action onCleared)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");
            Contract.Requires<ArgumentNullException>(onAdded != null, "onAdded");
            Contract.Requires<ArgumentNullException>(onRemoved != null, "onRemoved");
            Contract.Requires<ArgumentNullException>(onCleared != null, "onCleared");
            return source.ActivateWithClear(onAdded, onRemoved, onCleared, err => { throw err; });
        }

        #endregion

        internal static IEnumerableExpression<T> HideIdentity<T>(this IEnumerableExpression<T> source)
        {
            Contract.Requires<ArgumentNullException>(source != null, "source");
            return CreateWithDisposable<T>(source.Activate);
        }
    }
}
